//
//  BaseObject.m
//  NomalTest
//
//  Created by 陈小东 on 15/3/14.
//  Copyright (c) 2015年 _____stone_chenxiaodong_____. All rights reserved.
//

#import "BaseObject.h"
#import "NSObject+AutoDescription.h"
#import <objc/runtime.h>
#import "NSString+HTML.h"

@implementation BaseObject

#pragma mark - Public Methods
- (id)initWithDictionary:(NSDictionary *)JSON{
    NSDictionary *JSONKeysToPropertyKeys = [self JSONKeysToPropertyKeys];
    
    if (![JSON isKindOfClass:[NSDictionary class]]) {
        //        *error = [NSError errorWithDomain:@"Invalid JSON" code:-1 userInfo:nil];
        return (id)JSON;
    }
    
    for (NSString *jsonKey in JSON) {
        NSString *propertyKey = JSONKeysToPropertyKeys[jsonKey] ? JSONKeysToPropertyKeys[jsonKey] : jsonKey;
        
        if ([self respondsToSelector:NSSelectorFromString(propertyKey)]) {
            Class class = NSClassFromString([self getPropertyTypeString:propertyKey]);
            if ([class isSubclassOfClass:[BaseObject class]]) {
                // if JSON[jsonKey] is NSNull class, ignore it. so it becomes nil.
                if (![JSON[jsonKey] isKindOfClass:[NSNull class]]) {
                    // init it with the right class
                    BaseObject *value = [[class alloc] initWithDictionary:JSON[jsonKey]];
                    [self setValue:value forKey:propertyKey];
                }
            } else {
                if (![JSON[jsonKey] isKindOfClass:[NSNull class]]) {
                    if ([JSON[jsonKey] isKindOfClass:[NSString class]]) {
                        NSString *value = [((NSString *)JSON[jsonKey]) stringByDecodingHTMLEntities];
                        value = [value stringByReplacingOccurrencesOfString:@"<br>" withString:@"\n"];
                        [self setValue:value forKey:propertyKey];
                    } else {
                        [self setValue:JSON[jsonKey] forKey:propertyKey];
                    }
                }
            }
        }
    }
    return self;
}

#pragma mark - Utils

- (NSString *)getPropertyTypeString:(NSString *)propertyName{
    objc_property_t property = class_getProperty([self class], [propertyName UTF8String]);
    // propertyAttributes looks like: T@"NSArray",&,Vstuff
    const char * propertyAttributes = property_getAttributes(property);
    // suck out the type within attributes
    NSArray *sections = [[NSString stringWithUTF8String:propertyAttributes] componentsSeparatedByString:@"\""];
    
    NSString *type;
    // something like T@"ViewController",&,N,V_viewController
    if (sections.count == 3) {
        type = sections[1];
    }
    // if it is primitive value, it's like Ti,N,V_number
    return type;
}

- (NSDictionary *)JSONKeysToPropertyKeys{
    return @{};
}

- (NSString *)description{
    return [self autoDescription];
}

#pragma mark - NSCoding

- (void)encodeWithCoder:(NSCoder *)aCoder{
    unsigned int propertyCount;
    objc_property_t *properties = class_copyPropertyList([self class], &propertyCount);
    
    for (int i = 0; i < propertyCount; i++) {
        objc_property_t property = properties[i];
        char *propertyType = property_copyAttributeValue(property, "T");
        NSString *propertyName = [NSString stringWithFormat:@"%s", property_getName(property)];
        char *iVar = property_copyAttributeValue(property, "V");
        NSString *iVarName = [NSString stringWithFormat:@"%s", iVar];
        free(iVar);
        
        @try {
            switch (propertyType[0]) {
                case 'f': // float
                    [aCoder encodeFloat:[[self valueForKey:iVarName] floatValue] forKey:propertyName];
                    break;
                case 'd': // double
                    [aCoder encodeDouble:[[self valueForKey:iVarName] doubleValue] forKey:propertyName];
                    break;
                case 'i': // int
                case 's': // short
                case 'l': // long
                case 'q': // long long
                case 'I': // unsigned int
                case 'S': // unsigned short
                case 'L': // unsigned long
                case 'Q': // unsigned long long
                    [aCoder encodeInteger:[[self valueForKey:iVarName] intValue] forKey:propertyName];
                    break;
                case 'B': // BOOL
                    [aCoder encodeBool:[[self valueForKey:iVarName] boolValue] forKey:propertyName];
                    break;
                default:
                    [aCoder encodeObject:[self valueForKey:iVarName] forKey:propertyName];
            }
        }
        @catch (NSException *exception) {
            NSLog(@"encodeException:%@", exception);
        }
        free (propertyType);
    }
    free(properties);
}

- (id)initWithCoder:(NSCoder *)aDecoder{
    self = [super init];
    if (self) {
        unsigned int propertyCount;
        objc_property_t *properties = class_copyPropertyList([self class], &propertyCount);
        
        for (int i = 0; i < propertyCount; i++) {
            objc_property_t property = properties[i];
            char *propertyType = property_copyAttributeValue(property, "T");
            NSString *propertyName = [NSString stringWithFormat:@"%s", property_getName(property)];
            char *iVar = property_copyAttributeValue(property, "V");
            NSString *iVarName = [NSString stringWithFormat:@"%s", iVar];
            free(iVar);
            
            @try {
                switch (propertyType[0]) {
                    case 'f': // float
                        [self setValue:[NSNumber numberWithFloat:[aDecoder decodeFloatForKey:propertyName]] forKey:iVarName];
                        break;
                    case 'd': // double
                        [self setValue:[NSNumber numberWithDouble:[aDecoder decodeDoubleForKey:propertyName]] forKey:iVarName];
                        break;
                    case 'i': // int
                    case 's': // short
                    case 'l': // long
                    case 'q': // long long
                    case 'I': // unsigned int
                    case 'S': // unsigned short
                    case 'L': // unsigned long
                    case 'Q': // unsigned long long
                        [self setValue:[NSNumber numberWithInteger:[aDecoder decodeIntegerForKey:propertyName]] forKey:iVarName];
                        break;
                    case 'B':
                        [self setValue:[NSNumber numberWithBool:[aDecoder decodeBoolForKey:propertyName]] forKey:iVarName];
                        break;
                    default:
                        if ([aDecoder containsValueForKey:propertyName]) {
                            [self setValue:[aDecoder decodeObjectForKey:propertyName] forKey:iVarName];
                        }
                }
            }
            @catch (NSException *exception) {
                NSLog(@"setValueException:%@", exception);
            }
            free(propertyType);
        }
        free(properties);
    }
    return self;
}

#pragma mark - NSCopying

- (id)copyWithZone:(NSZone *)zone{
    id object = [[[self class] allocWithZone:zone] init];
    
    unsigned int propertyCount;
    objc_property_t *properties = class_copyPropertyList([self class], &propertyCount);
    
    for (int i = 0; i < propertyCount; i++) {
        objc_property_t property = properties[i];
        char * attributeValue = property_copyAttributeValue(property, "V");
        NSString *iVarName = [NSString stringWithFormat:@"%s", attributeValue];
        free(attributeValue);
        [object setValue:[self valueForKey:iVarName] forKey:iVarName];
    }
    free(properties);
    return object;
}

@end
